﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;

namespace TestApp
{
    public class BlockGenerator
    {
        const int BlOCK_WIDTH = 4;
        const int BLOCK_DEPTH = 4;
        static Random rand = new Random();

        private class ConnPoint
        {
            public bool IsConnected;
            static Random rand = new Random();

            public void Random()
            {
                this.IsConnected = (rand.NextDouble() < 0.25);
            }
        }

        private class ConnectionSet
        {
            public ConnPoint Top;
            public ConnPoint Bottom;
            public ConnPoint Left;
            public ConnPoint Right;

            static Random rand = new Random();

            public ConnectionSet()
            {
                Top = new ConnPoint();
                Bottom = new ConnPoint();
                Left = new ConnPoint();
                Right = new ConnPoint();
            }

            public ConnectionSet(ConnectionSet connectionSet, int side) : this()
            {
                if (side == 0)
                {
                    this.Bottom = connectionSet.Top;
                }
                else
                {
                    this.Left = connectionSet.Right;
                }
            }

            public ConnectionSet(ConnectionSet connectionSet1, ConnectionSet connectionSet2) : this()
            {
                this.Left = connectionSet1.Right;
                this.Bottom = connectionSet2.Top;
            }

            public static ConnectionSet[,] GenerateBlock(int width, int depth)
            {
                ConnectionSet[,] set = new ConnectionSet[width - 1, depth - 1];

                for (int y = 0; y < set.GetLength(1); y++)
                {
                    for (int x = 0; x < set.GetLength(0); x++)
                    {
                        if (x == 0 && y == 0)
                        {
                            set[x, y] = new ConnectionSet();
                        }
                        else if (x == 0)
                        {
                            set[x, y] = new ConnectionSet(set[x, y-1], 0);
                        }
                        else if (y == 0)
                        {
                            set[x, y] = new ConnectionSet(set[x - 1, y], 1);
                        }
                        else
                        {
                            set[x, y] = new ConnectionSet(set[x - 1, y], set[x, y-1]);
                        }
                        set[x, y].Top.Random();
                        set[x, y].Bottom.Random();
                        set[x, y].Left.Random();
                        set[x, y].Right.Random();
                    }
                    
                }

                FixLs(set);
                ExposeCenter(set);
                FixLs(set);

                Console.WriteLine(GetString(set));
                return set;
            }

            private static void ExposeCenter(ConnectionSet[,] set)
            {
                int width = set.GetLength(0);
                int depth = set.GetLength(1);
                for (int x = 1; x <= (width / 2); x++)
                {
                    for (int y = 1; y <= (depth / 2); y++)
                    {
                        switch (set[x, y].GetCount())
                        {
                            case 0:
                                Expose(set[x - 1, y - 1].Top, set[x - 1, y - 1].Right);
                                Expose(set[x - 1, y + 1].Bottom, set[x - 1, y + 1].Right);
                                Expose(set[x + 1, y - 1].Top, set[x + 1, y - 1].Left);
                                Expose(set[x + 1, y + 1].Bottom, set[x + 1, y + 1].Left);
                                break;
                            case 1:
                                ExposeTri(set, x, y);
                                break;
                            case 2:
                                ExposeBi(set, x, y);
                                break;
                            case 4:
                                Expose(set, x, y);
                                break;
                        }
                    }
                }

            }

            private static void ExposeTri(ConnectionSet[,] set, int x, int y)
            {
                //the divide is horizontal... -|-
                if (set[x, y].Top.IsConnected)
                {
                    
                    Expose(set[x - 1, y - 1].Top, set[x - 1, y - 1].Right);
                    Expose(set[x + 1, y - 1].Top, set[x + 1, y - 1].Left);

                    if (
                        !(set[x - 1, y + 1].Bottom.IsConnected && set[x - 1, y + 1].Right.IsConnected &&
                        set[x + 1, y + 1].Bottom.IsConnected && set[x + 1, y + 1].Left.IsConnected))
                    {
                        switch (rand.Next(2))
                        {
                            case 0:
                                Expose(set[x - 1, y + 1].Bottom, set[x - 1, y + 1].Right);
                                break;
                            case 1:
                                Expose(set[x + 1, y + 1].Bottom, set[x + 1, y + 1].Left);
                                break;
                        }
                    }
                    
                }
                else if (set[x, y].Bottom.IsConnected)// _|_
                {
                    //expose top left and right
                    Expose(set[x - 1, y + 1].Bottom, set[x - 1, y + 1].Right);
                    Expose(set[x + 1, y + 1].Bottom, set[x + 1, y + 1].Left);
                    //expose bottom
                    if (
                        !(set[x - 1, y - 1].Top.IsConnected && set[x - 1, y - 1].Right.IsConnected &&
                        set[x + 1, y - 1].Top.IsConnected && set[x + 1, y - 1].Left.IsConnected))
                    {
                        switch (rand.Next(2))
                        {
                            case 0:
                                Expose(set[x - 1, y - 1].Top, set[x - 1, y - 1].Right);
                                break;
                            case 1:
                                Expose(set[x + 1, y - 1].Top, set[x + 1, y - 1].Left);
                                break;
                        }
                    }
                }
                else if (set[x, y].Left.IsConnected)// |-
                {
                    Expose(set[x + 1, y + 1].Bottom, set[x + 1, y + 1].Left);
                    Expose(set[x + 1, y - 1].Top, set[x + 1, y - 1].Left);
                    
                    if (
                        !(set[x - 1, y + 1].Bottom.IsConnected && set[x - 1, y + 1].Right.IsConnected &&
                        set[x + 1, y + 1].Bottom.IsConnected && set[x + 1, y + 1].Left.IsConnected))
                    {
                        switch (rand.Next(2))
                        {
                            case 0:
                                Expose(set[x - 1, y + 1].Bottom, set[x - 1, y + 1].Right);
                                break;
                            case 1:
                                Expose(set[x - 1, y - 1].Top, set[x - 1, y - 1].Right);
                                break;
                        }
                    }
                    
                }
                else if (set[x, y].Right.IsConnected)// -|
                {
                    Expose(set[x - 1, y + 1].Bottom, set[x - 1, y + 1].Right);
                    Expose(set[x - 1, y - 1].Top, set[x - 1, y - 1].Right);
                    
                    if (
                        !(set[x + 1, y + 1].Bottom.IsConnected && set[x + 1, y + 1].Left.IsConnected &&
                        set[x + 1, y - 1].Top.IsConnected && set[x + 1, y - 1].Left.IsConnected))
                    {
                        switch (rand.Next(2))
                        {
                            case 0:
                                Expose(set[x + 1, y + 1].Bottom, set[x + 1, y + 1].Left);
                                break;
                            case 1:
                                Expose(set[x + 1, y - 1].Top, set[x + 1, y - 1].Left);
                                break;
                        }
                    }
                }
            }

            private static void ExposeBi(ConnectionSet[,] set, int x, int y)
            {
                //the divide is horizontal...
                if (set[x, y].Top.IsConnected)
                {
                    if (
                        !(set[x - 1, y + 1].Bottom.IsConnected && set[x - 1, y + 1].Right.IsConnected &&
                        set[x + 1, y + 1].Bottom.IsConnected && set[x + 1, y + 1].Left.IsConnected))
                    {
                        switch (rand.Next(2))
                        {
                            case 0:
                                Expose(set[x - 1, y + 1].Bottom, set[x - 1, y + 1].Right);
                                break;
                            case 1:
                                Expose(set[x + 1, y + 1].Bottom, set[x + 1, y + 1].Left);
                                break;
                        }
                    }
                    if (
                        !(set[x - 1, y - 1].Top.IsConnected && set[x - 1, y - 1].Right.IsConnected &&
                        set[x + 1, y - 1].Top.IsConnected && set[x + 1, y - 1].Left.IsConnected))
                    {
                        switch (rand.Next(2))
                        {
                            case 0:
                                Expose(set[x - 1, y - 1].Top, set[x - 1, y - 1].Right);
                                break;
                            case 1:
                                Expose(set[x + 1, y - 1].Top , set[x + 1, y - 1].Left);
                                break;
                        }
                    }
                }
                else // vertical divide
                {
                    if (
                        !(set[x - 1, y + 1].Bottom.IsConnected && set[x - 1, y + 1].Right.IsConnected &&
                        set[x + 1, y + 1].Bottom.IsConnected && set[x + 1, y + 1].Left.IsConnected))
                    {
                        switch (rand.Next(2))
                        {
                            case 0:
                                Expose(set[x - 1, y + 1].Bottom, set[x - 1, y + 1].Right);
                                break;
                            case 1:
                                Expose(set[x - 1, y - 1].Top, set[x - 1, y - 1].Right);
                                break;
                        }
                    }
                    if (
                        !(set[x + 1, y + 1].Bottom.IsConnected && set[x + 1, y + 1].Left.IsConnected &&
                        set[x + 1, y - 1].Top.IsConnected && set[x + 1, y - 1].Left.IsConnected))
                    {
                        switch (rand.Next(2))
                        {
                            case 0:
                                Expose(set[x + 1, y + 1].Bottom, set[x + 1, y + 1].Left);
                                break;
                            case 1:
                                Expose(set[x + 1, y - 1].Top, set[x + 1, y - 1].Left);
                                break;
                        }
                    }
                }
            }

            private static void Expose(ConnectionSet[,] set, int x, int y)
            {
                if (!(set[x - 1, y - 1].Top.IsConnected && set[x - 1, y - 1].Right.IsConnected &&
                    set[x - 1, y + 1].Bottom.IsConnected && set[x - 1, y + 1].Right.IsConnected &&
                    set[x + 1, y - 1].Top.IsConnected && set[x + 1, y - 1].Left.IsConnected &&
                    set[x + 1, y + 1].Bottom.IsConnected && set[x + 1, y + 1].Left.IsConnected))
                {
                    switch (rand.Next(4))
                    {
                        case 0:
                            Expose(set[x - 1, y - 1].Top, set[x - 1, y - 1].Right);
                            break;
                        case 1:
                            Expose(set[x - 1, y + 1].Bottom, set[x - 1, y + 1].Right);
                            break;
                        case 2:
                            Expose(set[x + 1, y - 1].Top, set[x + 1, y - 1].Left);
                            break;
                        case 3:
                            Expose(set[x + 1, y + 1].Bottom, set[x + 1, y + 1].Left);
                            break;
                    }
                }
            }

            private static void Expose(ConnPoint connPoint1, ConnPoint connPoint2)
            {
                if (!connPoint1.IsConnected && !connPoint2.IsConnected)
                {
                    if (rand.Next(2) == 0)
                    {
                        connPoint1.IsConnected = true;
                    }
                    else
                    {
                        connPoint2.IsConnected = true;
                    }
                }
            }

            private static void RecurseFix(ConnectionSet[,] set, int x, int y)
            {
                if (set[x, y].FixLs())
                {
                    if (x > 0)
                    {
                       RecurseFix(set, x - 1, y);
                    }
                    if (y > 0)
                    {
                        RecurseFix(set, x, y-1);
                    }
                    if (x + 1 < set.GetLength(0))
                    {
                        RecurseFix(set, x + 1, y);
                    }
                    if (y+1 < set.GetLength(1))
                    {
                        RecurseFix(set, x, y + 1);
                    }
                }
            }

            private static void FixLs(ConnectionSet[,] set)
            {

                for (int y = 0; y < set.GetLength(1); y++)
                {
                    for (int x = 0; x < set.GetLength(0); x++)
                    {
                        RecurseFix(set, x, y);
                    }
                }

            }

            private bool FixLs()
            {
                if (CreatesCorner() || NonsensicalDevision())
                {
                    this.Top.IsConnected = true;
                    this.Bottom.IsConnected = true;
                    this.Left.IsConnected = true;
                    this.Right.IsConnected = true;
                    return true;
                }
                return false;
            }

            private int GetCount()
            {
                int count = 0;
                if (this.Top.IsConnected)
                {
                    count++;
                }
                if (this.Bottom.IsConnected)
                {
                    count++;
                }
                if (this.Left.IsConnected)
                {
                    count++;
                }
                if (this.Right.IsConnected)
                {
                    count++;
                }
                return count;
            }

            private bool NonsensicalDevision()
            {
                int count = GetCount();
                if (count == 3)
                {
                    return true;
                }
                return false;
            }

            private bool CreatesCorner()
            {
                if (this.Top.IsConnected && this.Left.IsConnected && !(this.Bottom.IsConnected || this.Right.IsConnected))
                {
                    return true;
                }
                if (this.Bottom.IsConnected && this.Left.IsConnected && !(this.Top.IsConnected || this.Right.IsConnected))
                {
                    return true;
                }
                if (this.Top.IsConnected && this.Right.IsConnected && !(this.Bottom.IsConnected || this.Left.IsConnected))
                {
                    return true;
                }
                if (this.Bottom.IsConnected && this.Right.IsConnected && !(this.Top.IsConnected || this.Left.IsConnected))
                {
                    return true;
                }
                return false;
            }

            public static String GetString(ConnectionSet[,] set) 
            {
                String ret = "";
                string bottom = "|";
                for (int y = set.GetLength(1) - 1; y >= 0; y--)
                {
                    ret += "|";
                    
                    for (int x = 0; x < set.GetLength(0); x++)
                    {
                        
                        if (x == 0 && !set[x,y].Left.IsConnected)
                        {
                            ret += "_";
                        }
                        else if (x == 0)
                        {
                            ret += " ";
                        }
                        if (!set[x,y].Top.IsConnected)
                        {
                            ret += "|";
                        }
                        else
                        {
                            ret += " ";
                        }
                        if (!set[x, y].Right.IsConnected)
                        {
                            ret += "_";
                        }
                        else
                        {
                            ret += " ";
                        }
                        if (y == 0 && !set[x, y].Bottom.IsConnected)
                        {
                            bottom += " |";
                        }
                        else if (y == 0)
                        {
                            bottom += "  ";
                        }
                        
                    }
                    ret += "|\r\n";
                }
                return ret + bottom + " |";
            }
        }

        public class Vector2
        {
            public float x;
            public float y;
        }

        private static void SetBlocks(Vector2[,,] blockSize)
        {
            float xRemaining = 1.0f;
            float yRemaining = 1.0f;

            float xmin = 1.0f / (float)(blockSize.GetLength(0)+1);
            float ymin = 1.0f / (float)(blockSize.GetLength(1)+1);

//            Console.WriteLine("Xmin: " + xmin);
//            Console.WriteLine("XMax: " + (xRemaining - (xmin * (blockSize.GetLength(0)-1))));
//            Console.WriteLine("Ymin: " + ymin);

            float totalWidth = 0.0f;
            float totalDepth = 0.0f;
            for (int x = 0, xLeft = blockSize.GetLength(0)-1; x < blockSize.GetLength(0); x++, xLeft--)
            {
//                Console.WriteLine("Max: " + (xRemaining - (xmin * (float)(xLeft + 1))));
                float widthp = xmin + (float)(rand.NextDouble() * (xRemaining - (xmin * (float)(xLeft + 1))));
                xRemaining -= widthp;
                totalWidth += widthp;
                blockSize[x, 0,0] = new Vector2();
                blockSize[x,0,0].x = widthp;
//                Console.WriteLine("X"+x+": "+widthp);
            }
//            Console.WriteLine("Width: " + totalWidth);
            for (int y = 0, yleft = blockSize.GetLength(0)-1; y < blockSize.GetLength(0); y++, yleft--)
            {
                float depthp = ymin + (float)(rand.NextDouble() * (yRemaining - (ymin * (float)(yleft+1))));
                yRemaining -= depthp;
                totalDepth += depthp;
                blockSize[0, y,0] = new Vector2();
                blockSize[0,y,0].y = depthp;
//                Console.WriteLine("Y" + y + ": " + depthp);
            }
//            Console.WriteLine("Depth: " + totalDepth);
            for (int x = 1; x < blockSize.GetLength(0); x++)
            {
                for (int y = 1; y < blockSize.GetLength(1); y++)
                {
                    blockSize[x, y,0] = new Vector2();
                    blockSize[x,y,0].x = blockSize[x,0,0].x;
                    blockSize[x,y,0].y = blockSize[0,y,0].y;
                }
            }
            
        }

        private static void SetPlaces(Vector2[, ,] blockSize)
        {
            float width = 0.0f;
            float depth = 0.0f;

            for (int x = 0; x < blockSize.GetLength(0); x++)
            {
                width += blockSize[x, 0, 0].x;
            }
            for (int y = 0; y < blockSize.GetLength(1); y++)
            {
                depth += blockSize[0, y, 0].y;
            }

            float leftoverWidth = 1.0f - width;
            float leftoverDepth = 1.0f - depth;

            float widthSeperation = leftoverWidth / (blockSize.GetLength(0) + 1);
            float depthSeperation = leftoverDepth / (blockSize.GetLength(1) + 1);

            float accWidth = widthSeperation;
            float accDepth = depthSeperation;

            for (int x = 0; x < blockSize.GetLength(0); x++)
            {
                for (int y = 0; y < blockSize.GetLength(1); y++)
                {
                    blockSize[x, y, 1] = new Vector2();
                    blockSize[x, y, 1].x = accDepth;
                }
                accWidth += blockSize[x, 0, 0].x + widthSeperation;
            }

            for (int y = 1; y < blockSize.GetLength(1); y++) 
            {
                for (int x = 1; x < blockSize.GetLength(0); x++)
                {
                    blockSize[x, y, 1].y = accDepth;
                }
                accDepth += blockSize[0, y, 0].y + depthSeperation;
            }

        }

        private static void ApplyDimensions(Vector2[, ,] footprints, float width, float depth)
        {
            for (int x = 0; x < footprints.GetLength(0); x++)
            {
                for (int y = 0; y < footprints.GetLength(1); y++)
                {
                    footprints[x, y, 0].x *= width;
                    footprints[x, y, 0].y *= depth;
                    footprints[x, y, 1].x *= width;
                    footprints[x, y, 1].y *= depth;
                }
            }
        }

        public static Vector2[, ,] GenerateBuildingFootprints(float width, float depth)
        {


            Vector2[, ,] footprints = new Vector2[BlOCK_WIDTH, BLOCK_DEPTH, 2];

            ConnectionSet[,] ConBlockSet = ConnectionSet.GenerateBlock(BlOCK_WIDTH, BLOCK_DEPTH);

            SetBlocks(footprints);
            SetPlaces(footprints);
            ApplyDimensions(footprints, width, depth);

            return footprints;
        }

        


    }

    class Program
    {

        static void Main(string[] args)
        {
            String[] stringList = new String[] { "mid_1_2_3", "low_1_3_3", "heigh_12_2_3", "mid_1_2_3", "mid_1_2_3" };
            while (true)
            {
                foreach (string name in stringList)
                {
                    Match match = Regex.Match(name, @"(?<size>(low)|(mid)|(high))_(?<number>[\d]+)_(?<width>[\d]+)_(?<height>[\d]+)");
                    Console.WriteLine("size: "+match.Groups["size"] );
                    Console.WriteLine("width: " + match.Groups["width"]);
                    Console.WriteLine("height: " + match.Groups["height"]);
                }
                //BlockGenerator.GenerateBuildingFootprints(50f, 50f);
                Console.ReadLine();
            }
        }
    }

}